using DotNetCommon.Extensions;
using DotNetCommon.Logger;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace DotNetCommon.Expressions
{
    /// <summary>
    /// 表达式树节点
    /// </summary>
    internal class ExpressionNode
    {
        private static readonly ILogger<ExpressionNode> logger = LoggerFactory.CreateLogger<ExpressionNode>();
        /// <summary>
        /// 是否是根节点
        /// </summary>
        internal bool IsRoot { get; set; }
        /// <summary>
        /// 当前节点的表达式(可能为空,比如: Call[静态方法] 的 Object)
        /// </summary>
        internal Expression Expression { get; set; }
        /// <summary>
        /// 表达式类型(可能为空,比如: Call[静态方法] 的 Object)
        /// </summary>
        internal ExpressionType NodeType => Expression.NodeType;
        /// <summary>
        /// 子节点
        /// </summary>
        internal List<ExpressionNode> Children { get; set; }
        /// <summary>
        /// 父节点
        /// </summary>
        internal ExpressionNode Parent { get; set; }

        /// <summary>
        /// 当前节点有更新,请求父节点 rebuild
        /// </summary>
        internal bool _updateRequest { get; set; }

        /// <summary>
        /// 当前节点是否有更新
        /// </summary>
        internal bool NeedUpdate => Children.IsNullOrEmpty() ? _updateRequest : Children.Any(i => i.NeedUpdate);

        /// <summary>
        /// 当前所处scope的参数列表(表达式树中可能存在多层lambda嵌套(没办法: 有的方法接受func或expression))
        /// </summary>
        /// <remarks>注意: null 表示没有(非 lambda 节点), emptyOrHasElements 表示 lambda 节点有或没有参数</remarks>
        internal List<ParameterExpression> _parameters;

        /// <summary>
        /// 当前节点所处scope的参数列表(注意:使用时判断当前节点是否是 lambda,因为 lambda 节点的 Parameters 是给下级用的,需要上级的话要显示指定 Parent?.Parameters)
        /// </summary>
        internal List<ParameterExpression> Parameters => _parameters ?? Parent?.Parameters;

        /// <summary>
        /// 当前节点的完整唯一描述(基于此实现缓存,和具体值无关,只和表达式写法和Type有关)
        /// </summary>
        internal string FullMarkString { get; set; }
        /// <summary>
        /// 当前节点是否是参数(当前节点是参数并不一定不能简化,lambda 嵌套的时候,内层lambda也可能有参数,但不妨碍 整个内层 lambda 被简化掉)
        /// </summary>
        internal bool IsParameter { get; set; }
        /// <summary>
        /// 假的参数(用户手动指定保留node后,通过 FakeParameter 阻止 Scope 内父级对其简化)
        /// </summary>
        internal ParameterExpression FakeParameter { get; set; }
        /// <summary>
        /// 当前节点是否含源参数(当前节点所处 scope 的,注意: 当前节点是 lambda 的时候要用父节点的 paramenters)
        /// </summary>
        internal bool HasParameter
        {
            get
            {
                if (Expression != null && NodeType == ExpressionType.Lambda)
                {
                    //lambda本身属于父级 scope
                    return hasParameter(Parent?.Parameters);
                }
                return hasParameter(Parameters);
            }
        }
        /// <summary>
        /// 检查自身或子节点是否引用当前scope中的参数
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        internal bool hasParameter(List<ParameterExpression> parameters)
        {
            if (parameters.IsNullOrEmpty()) return false;
            if (IsParameter) return parameters.Any(i => i == Expression || i == FakeParameter);
            return Children.IsNotNullOrEmpty() && Children.Any(i => i.hasParameter(parameters));
        }
        /// <summary>
        /// 当前节点是否是局部变量
        /// </summary>
        internal bool IsLocalVariable { get; set; }

        /// <summary>
        /// 表达式编译缓存
        /// </summary>
        private static ConcurrentDictionary<string, Delegate> _caches = new ConcurrentDictionary<string, Delegate>();
        /// <summary>
        /// 存储简化后计算的常量(根节点有一个就足够了)
        /// </summary>
        internal Dictionary<ParameterExpression, object> _outMidValues = null;
        internal Dictionary<ParameterExpression, object> outMidValues => _outMidValues ?? Parent?.outMidValues;

        /// <summary>
        /// 实际的简化方法: 将常量提取成参数,rebuid一个表达式出来,编译后并缓存,调用时将哪些常量值都传进去
        /// </summary>
        internal object Reduce()
        {
            var consts = new List<(object Value, Type Type)>();
            var parameters = new List<ParameterExpression>();
            Visit(this);
            //使用FullMarkString对于那些复杂 Object 是否能区别开? 比如: 常量 dic:{"name","小明"} 和 dic: {"age":18}
            //这种非基础类型的常量不可能直接出现在表达式中,只可能是由子节点简化而来,他们简化后都是被放进 outMidValues 中
            var dele = _caches.GetOrAdd(FullMarkString, key =>
            {
                var lambda = Expression.Lambda(Expression, parameters.ToArray());
                var dele = lambda.Compile();
                logger.LogDebug($"Reduce: lambda.Compile,FullMarkString={FullMarkString}");
                return dele;
            });
            return dele.DynamicInvoke(consts.Select(i => i.Value).ToArray());

            void Visit(ExpressionNode node)
            {
                if (node.Expression == null) return;
                if (node.Children.IsNotNullOrEmpty())
                {
                    //当外层 lambda 简化内层 lambda 的时候,内层 lambda 下的 parameter 没有 children
                    for (int i = 0; i < node.Children.Count; i++)
                    {
                        Visit(node.Children[i]);
                    }
                }
                if (node.NodeType == ExpressionType.Constant)
                {
                    //如果是常量则提取和替换
                    var item = ((node.Expression as ConstantExpression).Value, node.Expression.Type);
                    consts.Add(item);
                    var para = Expression.Parameter(item.Type);
                    node.Expression = para;
                    parameters.Add(para);
                    //不需要 简化过程中肯定会 rebuild ,最终简化后的结果肯定不会再参与简化一次(同一scope内),而且简化后整体变成了 parameter,在外层处理 rebuid
                    //node._updateRequest = true;
                }
                else if (node.NodeType == ExpressionType.Parameter)
                {
                    //内层lamda被简化后,值放到了 outMidValues 中,式中是 parameter
                    var p = node.Expression as ParameterExpression;
                    if (outMidValues.ContainsKey(p))
                    {
                        var item = (outMidValues[p], p.Type);
                        consts.Add(item);
                        parameters.Add(p);
                    }
                }
                else
                {
                    //非常量则重新组装 Expression
                    var visit = ExpressionHelper.GetVisit(node.NodeType);
                    node.Expression = visit.Rebuild(node);
                }
            }
        }
    }
}
